#pragma once
#include <core.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <linal/vector.h>
#include <boost/lambda/lambda.hpp>

namespace linal
{
    //============================================================================================================================
    //============================================================================================================================
    MATCHER_P4(ComponentsAre, component0, component1, component2, component3, "")
    {
        return (arg.dimension() == 4 &&
            ::testing::Matches(::testing::DoubleEq(component0))(arg[0]) &&
            ::testing::Matches(::testing::DoubleEq(component1))(arg[1]) &&
            ::testing::Matches(::testing::DoubleEq(component2))(arg[2]) &&
            ::testing::Matches(::testing::DoubleEq(component3))(arg[3]));
    }

    MATCHER_P5(ComponentsAre, component0, component1, component2, component3, component4, "")
    {
        return (arg.dimension() == 5 &&
            ::testing::Matches(::testing::DoubleEq(component0))(arg[0]) &&
            ::testing::Matches(::testing::DoubleEq(component1))(arg[1]) &&
            ::testing::Matches(::testing::DoubleEq(component2))(arg[2]) &&
            ::testing::Matches(::testing::DoubleEq(component3))(arg[3]) &&
            ::testing::Matches(::testing::DoubleEq(component4))(arg[4]));
    }

    MATCHER_P6(ComponentsAre, component0, component1, component2, component3, component4, component5, "")
    {
        return (arg.dimension() == 6 &&
            ::testing::Matches(::testing::DoubleEq(component0))(arg[0]) &&
            ::testing::Matches(::testing::DoubleEq(component1))(arg[1]) &&
            ::testing::Matches(::testing::DoubleEq(component2))(arg[2]) &&
            ::testing::Matches(::testing::DoubleEq(component3))(arg[3]) &&
            ::testing::Matches(::testing::DoubleEq(component4))(arg[4]) &&
            ::testing::Matches(::testing::DoubleEq(component5))(arg[5]));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, CreationOfZeroSizeVector)
    {
        EXPECT_EQ(0, Vector().dimension());
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, CreationOfVector)
    {
        // Если вектор создаётся неправильно - дальнейшее тестирование бессмысленно.
        ASSERT_THAT(Vector(5, boost::lambda::constant(5.) - boost::lambda::_1), ComponentsAre(5., 4., 3., 2., 1.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, ZeroingOfVector)
    {
        Vector testVector(5, boost::lambda::_1);
        testVector.zeroize();
        EXPECT_THAT(testVector, ComponentsAre(0., 0., 0., 0., 0.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, CopyingOfVector)
    {
        Vector testVector(5, boost::lambda::constant(0.));
#ifndef NDEBUG
        testVector.resize(4);
        EXPECT_DEATH(Vector::copy(Vector(5, boost::lambda::_1), testVector),
                     "Assertion failed: source.dimension\\(\\) == destination.dimension\\(\\)");
        testVector.resize(6);
        EXPECT_DEATH(Vector::copy(Vector(5, boost::lambda::_1), testVector),
                     "Assertion failed: source.dimension\\(\\) == destination.dimension\\(\\)");
        testVector.resize(5);
#endif
        Vector::copy(Vector(5, boost::lambda::_1), testVector);
        EXPECT_THAT(testVector, ComponentsAre(0., 1., 2., 3., 4.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, ChangingOfVectorDimension)
    {
        Vector testVector(5, boost::lambda::_1);
        testVector.resize(4);
        EXPECT_THAT(testVector, ComponentsAre(0., 1., 2., 3.));
        testVector.resize(6);
        EXPECT_THAT(testVector, ComponentsAre(0., 1., 2., 3., 0., 0.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, AdditionOfVectors)
    {
        Vector testVector(5, boost::lambda::constant(0.));
#ifndef NDEBUG
        EXPECT_DEATH(Vector::add(Vector(5, boost::lambda::_1), Vector(4, boost::lambda::_1), testVector),
                     "Assertion failed: summand1.dimension\\(\\) == summand2.dimension\\(\\)");
        testVector.resize(4);
        testVector.zeroize();
        EXPECT_DEATH(Vector::add(Vector(5, boost::lambda::_1), Vector(5, boost::lambda::_1), testVector),
                     "Assertion failed: summand1.dimension\\(\\) == sum.dimension\\(\\)");
        testVector.resize(5);
        testVector.zeroize();
#endif
        Vector::add(Vector(5, boost::lambda::_1), Vector(5, boost::lambda::_1), testVector);
        EXPECT_THAT(testVector, ComponentsAre(0., 2., 4., 6., 8.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, SubtractionOfVectors)
    {
        Vector testVector(5, boost::lambda::constant(0.));
#ifndef NDEBUG
        EXPECT_DEATH(Vector::subtract(Vector(5, boost::lambda::_1), Vector(4, boost::lambda::_1), testVector),
                     "Assertion failed: minuend.dimension\\(\\) == subtrahend.dimension\\(\\)");
        testVector.resize(4);
        testVector.zeroize();
        EXPECT_DEATH(Vector::subtract(Vector(5, boost::lambda::_1), Vector(5, boost::lambda::_1), testVector),
                     "Assertion failed: minuend.dimension\\(\\) == difference.dimension\\(\\)");
        testVector.resize(5);
        testVector.zeroize();
#endif
        Vector::subtract(Vector(5, boost::lambda::_1), Vector(5, boost::lambda::_1), testVector);
        EXPECT_THAT(testVector, ComponentsAre(0., 0., 0., 0., 0.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, CalculationOfDotProduct)
    {
#ifndef NDEBUG
        EXPECT_DEATH(Vector::dotProduct(Vector(5, boost::lambda::_1), Vector(4, boost::lambda::_1)),
            "Assertion failed: v1.dimension\\(\\) == v2.dimension\\(\\)");
#endif
        EXPECT_THAT(Vector::dotProduct(Vector(5, boost::lambda::_1), Vector(5, boost::lambda::_1)), ::testing::DoubleEq(30.));
    }

    //============================================================================================================================
    //============================================================================================================================
    TEST(VectorTest, CalculationOfNorm)
    {
        EXPECT_THAT(Vector(5, boost::lambda::_1).norm(), ::testing::DoubleEq(std::sqrt(30.)));
        EXPECT_THAT(Vector(5, boost::lambda::_1).normSquared(), ::testing::DoubleEq(30.));
    }
}
